/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */
package org.bhf.providers.email;

import com.google.inject.Inject;
import org.bhf.email.eMailFacility;
import org.bhf.facilities.FacilityConfiguration;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Properties;

/**
 * SMTP Mail Facility supporting authentication and SSL;
 */
public class SMTPMailFacility implements eMailFacility
{
    private final Config            config;

    //
    // Configuration
    //

    @Inject
    public  SMTPMailFacility( final Config config )
    {
        this.config = config;
        this.config.configure( null, SMTPMailFacility.class, SMTPMailFacility.Config.class );
    }


    // For testing
    Config  getConfig() { return config; }
    public static class Config extends FacilityConfiguration
    {
        private String      host        = "127.0.0.1";
        private String      user;
        private String      password;
        private String[]    to;
        private String[]    cc;
        private String[]    bcc;
        private int         port        = 25;
        private boolean     useSSL      = false;
        private boolean     secure      = false;

        public String   getHost()
        {
            return host;
        }

        /**
         * SMTP host hostname. Required.
         * @param host SMTP host hostname
         */
        public void     setHost( final String host )
        {
            this.host = host;
        }

        public String   getUser()
        {
            return user;
        }

        /**
         * User for authentication to SMTP host.
         * @param user User for authentication to SMTP host.
         */
        public void     setUser( final String user )
        {
            this.user = user;
        }

        public String   getPassword()
        {
            return password;
        }

        /**
         * Password for authentication to SMTP host.
         * @param password Password for authentication to SMTP host.
         */
        public void     setPassword( final String password )
        {
            this.password = password;
        }

        public int      getPort()
        {
            return port;
        }

        /**
         * SMTP port, if not default.
         * @param port SMTP port
         */
        public void     setPort( final int port )
        {
            this.port = port;
        }

        public boolean  isUseSSL()
        {
            return useSSL;
        }

        /**
         * <code>true</code> to use SSL
         * @param useSSL <code>true</code> to use SSL
         */
        public void     setUseSSL( final boolean useSSL )
        {
            this.useSSL = useSSL;
        }

        public boolean isSecure()
        {
            return secure;
        }

        /**
         * <code>true</code> to use authentication
         * @param secure <code>true</code> to use authentication
         */
        public void     setSecure( final boolean secure )
        {
            this.secure = secure;
        }

        public String[] getTo()
        {
            return to;
        }

        /**
         * Additional 'to' addresses for each email sent. Optional.
         * @param to Additional 'to' addresses for each email sent. Optional.
         */
        public void     setTo( final String[] to )
        {
            this.to = to;
        }

        public String[] getCc()
        {
            return cc;
        }


        /**
         * Additional 'cc' addresses for each email sent. Optional.
         * @param cc Additional 'cc' addresses for each email sent. Optional.
         */
        public void     setCc( final String[] cc )
        {
            this.cc = cc;
        }

        public String[] getBcc()
        {
            return bcc;
        }


        /**
         * Additional 'bcc' addresses for each email sent. Optional.
         * @param bcc Additional 'bcc' addresses for each email sent. Optional.
         */
        public void     setBcc( final String[] bcc )
        {
            this.bcc = bcc;
        }
    }

    //
    // Facility Interface
    //

    /**
     * @inheritDoc
     * @exclude
     */
    public void     transportSend( final Session session, final Message message )
        throws MessagingException
    {
        if( config.to != null )
            for( final String a : config.to )
                message.addRecipient( Message.RecipientType.TO, new InternetAddress( a ) );
        
        if( config.cc != null )
            for( final String a : config.cc )
                message.addRecipient( Message.RecipientType.CC, new InternetAddress( a ) );

        if( config.bcc != null )
            for( final String a : config.bcc )
                message.addRecipient( Message.RecipientType.BCC, new InternetAddress( a ) );

        if( config.useSSL )
        {
            final Transport transport = session.getTransport();
            transport.connect(
                session.getProperty( "mail.smtps.host" ),
                Integer.parseInt( session.getProperty( "mail.smtps.port" ) ),
                session.getProperty( "mail.smtps.user" ),
                session.getProperty( "mail.smtps.password" )
            );
            try
            {
                transport.sendMessage( message, message.getRecipients( Message.RecipientType.TO ) );
            }
            finally
            {
                transport.close();
            }
        }
        else
        {
            Transport.send( message );
        }
    }

    /**
     * @inheritDoc
     * @exclude
     */
    public Session getSession()
    {
        final Properties properties = new Properties();

        if( config.useSSL )
        {
            properties.put( "mail.transport.protocol", "smtps" );
            properties.put( "mail.smtps.host", config.host );
            if( config.port != 0 )
                properties.put( "mail.smtps.port", Integer.toString( config.port ) );
            properties.put( "mail.smtps.auth", Boolean.toString( config.secure ) );
            properties.put( "mail.smtps.quitwait", "false" );
            if( config.user != null )
                properties.put( "mail.smtps.user", config.user );
            if( config.password != null )
                properties.put( "mail.smtps.password", config.password );
        }
        else
        {
            properties.put( "mail.transport.protocol", "smtp" );
            properties.put( "mail.smtp.host", config.host );

            // So that it'll work with localhosts that do not resolve to an IP
            try
            {
                InetAddress.getLocalHost().getHostName();
            }
            catch( UnknownHostException e )
            {
                properties.put( "mail.smtp.localhost", config.host );
            }

            if( config.port != 0 )
                properties.put( "mail.smtp.port", Integer.toString( config.port ) );
            properties.put( "mail.smtp.auth", Boolean.toString( config.secure ) );
            properties.put( "mail.smtp.quitwait", "false" );
            if( config.user != null )
                properties.put( "mail.smtp.user", config.user );
            if( config.password != null )
                properties.put( "mail.smtp.password", config.password );
        }

        return Session.getInstance(
            properties,
            new Authenticator()
            {
                @Override
                public PasswordAuthentication getPasswordAuthentication()
                {
                    return new PasswordAuthentication( config.user, config.password );
                }
            }
        );
    }

    /**
     * @inheritDoc
     * @exclude
     */
    public void validateConfiguration()
        throws Exception
    {
        if( config.host == null )
            throw new IllegalArgumentException( "SMTP host not specified" );
    }

    /**
     * @inheritDoc
     * @exclude
     */
    public eMailFacility decorate()
    {
        return this;
    }
}
